package com.lieve.tx.service;

import com.lieve.tx.entity.City;
import com.lieve.tx.mapper.CityMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;

/**
 * @see EnableTransactionManagement;
 * @see DataSourceTransactionManager;
 * @author sunyoboy
 * 2020/9/12
 */
@Slf4j
@Service
public class CityService {
    @Resource
    private CityMapper cityMapper;

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public City query() {
        update();
        insert();
        return cityMapper.findById(1L);
    }

    private void update() {
        if (true) {
            throw new RuntimeException("update");
        }
        cityMapper.updateById(City.builder().id(1L).name("hello").build());
    }

    private void insert() {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            /**
             * Invoked before transaction commit (before "beforeCompletion").
             * Can e.g. flush transactional O/R Mapping sessions to the database.
             * <p>This callback does <i>not</i> mean that the transaction will actually be committed.
             * A rollback decision can still occur after this method has been called. This callback
             * is rather meant to perform work that's only relevant if a commit still has a chance
             * to happen, such as flushing SQL statements to the database.
             * <p>Note that exceptions will get propagated to the commit caller and cause a
             * rollback of the transaction.
             *
             * @param readOnly whether the transaction is defined as read-only transaction
             * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
             *                          (note: do not throw TransactionException subclasses here!)
             * @see #beforeCompletion
             */
            @Override
            public void beforeCommit(boolean readOnly) {
                log.debug("beforeCommit");
            }

            /**
             * Invoked before transaction commit/rollback.
             * Can perform resource cleanup <i>before</i> transaction completion.
             * <p>This method will be invoked after {@code beforeCommit}, even when
             * {@code beforeCommit} threw an exception. This callback allows for
             * closing resources before transaction completion, for any outcome.
             *
             * @throws RuntimeException in case of errors; will be <b>logged but not propagated</b>
             *                          (note: do not throw TransactionException subclasses here!)
             * @see #beforeCommit
             * @see #afterCompletion
             */
            @Override
            public void beforeCompletion() {
                log.debug("beforeCompletion");
            }

            /**
             * Invoked after transaction commit. Can perform further operations right
             * <i>after</i> the main transaction has <i>successfully</i> committed.
             * <p>Can e.g. commit further operations that are supposed to follow on a successful
             * commit of the main transaction, like confirmation messages or emails.
             * <p><b>NOTE:</b> The transaction will have been committed already, but the
             * transactional resources might still be active and accessible. As a consequence,
             * any data access code triggered at this point will still "participate" in the
             * original transaction, allowing to perform some cleanup (with no commit following
             * anymore!), unless it explicitly declares that it needs to run in a separate
             * transaction. Hence: <b>Use {@code PROPAGATION_REQUIRES_NEW} for any
             * transactional operation that is called from here.</b>
             *
             * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
             *                          (note: do not throw TransactionException subclasses here!)
             */
            @Override
            public void afterCommit() {
                log.debug("afterCommit");
            }

            /**
             * Invoked after transaction commit/rollback.
             * Can perform resource cleanup <i>after</i> transaction completion.
             * <p><b>NOTE:</b> The transaction will have been committed or rolled back already,
             * but the transactional resources might still be active and accessible. As a
             * consequence, any data access code triggered at this point will still "participate"
             * in the original transaction, allowing to perform some cleanup (with no commit
             * following anymore!), unless it explicitly declares that it needs to run in a
             * separate transaction. Hence: <b>Use {@code PROPAGATION_REQUIRES_NEW}
             * for any transactional operation that is called from here.</b>
             *
             * @param status completion status according to the {@code STATUS_*} constants
             * @throws RuntimeException in case of errors; will be <b>logged but not propagated</b>
             *                          (note: do not throw TransactionException subclasses here!)
             * @see #STATUS_COMMITTED
             * @see #STATUS_ROLLED_BACK
             * @see #STATUS_UNKNOWN
             * @see #beforeCompletion
             */
            @Override
            public void afterCompletion(int status) {
                log.debug("afterCompletion : {}", status);
            }
        });
        cityMapper.insert(City.builder().id(2L).name("22").state("state").country("country").build());
    }
}
